using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using WindowsPhoneEssentials.Diagnostics;
using WindowsPhoneMVC.Navigation.Exceptions;
using WindowsPhoneMVC.Navigation.Interfaces;

namespace WindowsPhoneMVC.Navigation
{
    public class DefaultControllerLocator : IControllerLocator
    {
        private readonly List<Assembly> assembliesToScan;
        private volatile Dictionary<string, Type> controllers;
        private readonly object controllerLookupLock = new object();

        public DefaultControllerLocator(Assembly assemblyToScan, params Assembly[] additionalAssemblies)
        {
            assembliesToScan = new List<Assembly>(additionalAssemblies) { assemblyToScan };
        }

        public Dictionary<string, Type> Controllers
        {
            get
            {
                if (controllers == null)
                    lock (controllerLookupLock)
                        if (controllers == null)
                            using (TraceScope.Create(this, TraceLevel.Debug, () => "Scanning assemblies for controllers and building lookup"))
                            {
                                var controllerType = typeof (Controller);

                                controllers =
                                    assembliesToScan
                                        .SelectMany(a => a.GetTypes())
                                        .Where(t => t.IsClass && t.Name.EndsWith("Controller") && controllerType.IsAssignableFrom(t))
                                        .SelectMany(t => new[]
                                                             {
                                                                 new
                                                                     {
                                                                         Name = t.Name.Replace("Controller", string.Empty),
                                                                         Type = t
                                                                     },
                                                                 new
                                                                     {
                                                                         t.Name,
                                                                         Type = t
                                                                     }
                                                             })
                                        .ToDictionary(k => k.Name, k => k.Type);
                            }
                return controllers;
            }
        }

        public void AddAssemblyToScan(Assembly assembly)
        {
            //Invalidates cache
            assembliesToScan.Add(assembly);
            lock (controllerLookupLock)
                controllers = null;
        }

        public Type Locate(string controllerName)
        {
            using (TraceScope.Create(this, TraceLevel.Debug, () => string.Format("Locating controller {0}", controllerName)))
            {
                if (!Controllers.ContainsKey(controllerName))
                    throw new ControllerNotFoundException(controllerName);

                return Controllers[controllerName];
            }
        }
    }
}